package com.jfirer.mvc.core;

import com.jfirer.baseutil.StringUtil;
import com.jfirer.baseutil.bytecode.support.AnnotationContextFactory;
import com.jfirer.baseutil.bytecode.util.BytecodeUtil;
import com.jfirer.baseutil.reflect.ReflectUtil;
import com.jfirer.baseutil.smc.compiler.CompileHelper;
import com.jfirer.baseutil.uniqueid.AutumnId;
import com.jfirer.jfire.core.ApplicationContext;
import com.jfirer.jfire.core.BeanDefinition;
import com.jfirer.jfire.core.DefaultApplicationContext;
import com.jfirer.mvc.annotation.*;
import com.jfirer.mvc.config.MvcConfig;
import com.jfirer.mvc.core.action.Action;
import com.jfirer.mvc.core.action.ActionCenter;
import com.jfirer.mvc.core.action.ActionInitListener;
import com.jfirer.mvc.interceptor.ActionInterceptor;
import com.jfirer.mvc.resolver.ParamResolver;
import com.jfirer.mvc.resolver.impl.*;
import com.jfirer.mvc.util.ContentType;
import com.jfirer.mvc.util.UploadItem;
import com.jfirer.mvc.viewrender.DefaultResultType;
import com.jfirer.mvc.viewrender.RenderManager;
import com.jfirer.mvc.viewrender.impl.*;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

class ActionCenterBuilder
{

    public static ActionCenter generate(ClassLoader classLoader, ServletContext servletContext, String configClassName)
    {
        ApplicationContext context = readConfig(configClassName, classLoader);
        addViewRender(context);
        BeanDefinition beanDefinition = new BeanDefinition("servletContext", ServletContext.class, servletContext);
        context.registerBeanDefinition(beanDefinition);
        context.register(MvcConfig.class);
        context.refresh();
        ActionCenter actionCenter = new ActionCenter(generateActions(//
                servletContext.getContextPath().startsWith("/") ? servletContext.getContextPath() : "/" + servletContext.getContextPath(), //
                context//
        ).toArray(new Action[0]), context);
        return actionCenter;
    }

    static ApplicationContext readConfig(String configClassName, ClassLoader classLoader)
    {
        if (StringUtil.isNotBlank(configClassName) == false)
        {
            throw new NullPointerException();
        }
        try
        {
            Class<?> configClass = classLoader.loadClass(configClassName);
            return new DefaultApplicationContext(configClass, new CompileHelper(classLoader));
        }
        catch (ClassNotFoundException e)
        {
            ReflectUtil.throwException(e);
            return null;
        }
    }

    static void addViewRender(ApplicationContext context)
    {
        {
            context.register(JsonRender.class);
            context.register(HtmlRender.class);
            context.register(StringRender.class);
            context.register(RedirectRender.class);
            context.register(NoneRender.class);
            context.register(BytesRender.class);
        }
        {
            context.register(RenderManager.class);
        }
    }

    /**
     * 初始化Beancontext容器，并且抽取其中的ActionClass注解的类，将action实例化
     */
    static List<Action> generateActions(String contextUrl, ApplicationContext context)
    {
        List<BeanDefinition>     beans         = context.getBeanDefinitionsByAnnotation(Controller.class);
        List<BeanDefinition>     listenerBeans = context.getBeanDefinitions(ActionInitListener.class);
        List<ActionInitListener> tmp           = new LinkedList<ActionInitListener>();
        for (BeanDefinition each : listenerBeans)
        {
            tmp.add((ActionInitListener) each.getBean());
        }
        ActionInitListener[] listeners = tmp.toArray(new ActionInitListener[tmp.size()]);
        List<Action>         list      = new ArrayList<Action>();
        for (BeanDefinition each : beans)
        {
            list.addAll(generateActions(each, listeners, context, contextUrl));
        }
        for (ActionInitListener listener : listeners)
        {
            listener.initFinish();
        }
        return list;
    }

    /**
     * 创建某一个bean下面的所有action
     *
     * @param bean
     * @param listeners
     * @param contextUrl
     * @param jfire
     * @return
     */
    static List<Action> generateActions(BeanDefinition bean, ActionInitListener[] listeners, ApplicationContext jfire, String contextUrl)
    {
        AnnotationContextFactory annotationContextFactory = jfire.getAnnotationContextFactory();
        Class<?>                 src                      = bean.getType();
        String                   requestUrl               = contextUrl;
        if (annotationContextFactory.get(src).isAnnotationPresent(RequestMapping.class))
        {
            RequestMapping requestMapping = annotationContextFactory.get(src).getAnnotation(RequestMapping.class);
            requestUrl += requestMapping.value();
        }
        // 这里需要使用原始的类来得到方法，因为如果使用增强后的子类，就无法得到正确的方法名称以及方法上的注解信息
        Method[]     methods = getAllMehtods(bean.getType());
        List<Action> list    = new ArrayList<Action>();
        for (Method each : methods)
        {
            if (annotationContextFactory.get(each).isAnnotationPresent(RequestMapping.class))
            {
                Action action = buildAction(each, requestUrl, bean, jfire);
                list.add(action);
                for (ActionInitListener listener : listeners)
                {
                    listener.init(action);
                }
            }
        }
        return list;
    }

    static Method[] getAllMehtods(Class ckass)
    {
        List<Method> list = new ArrayList<Method>();
        while (ckass != Object.class)
        {
            for (Method each : ckass.getDeclaredMethods())
            {
                list.add(each);
            }
            ckass = ckass.getSuperclass();
        }
        return list.toArray(new Method[0]);
    }

    /**
     * 使用方法对象，顶级请求路径，容器对象初始化一个action实例。 该实例负责该action的调用
     *
     * @param method
     * @param requestPath 顶级请求路径，实际的请求路径为顶级请求路径/方法请求路径
     */
    public static Action buildAction(Method method, String requestPath, BeanDefinition bean, ApplicationContext applicationContext)
    {
        AnnotationContextFactory annotationContextFactory = applicationContext.getAnnotationContextFactory();
        RenderManager            renderManager            = applicationContext.getBean(RenderManager.class);
        Action                   action                   = new Action();
        action.setMethod(method);
        RequestMapping requestMapping = annotationContextFactory.get(method).getAnnotation(RequestMapping.class);
        action.setRequestMethods(requestMapping.method());
        action.setParamResolvers(generateParamResolvers(method));
        action.setActionEntity(bean.getBean());
        action.setHeaderRules(requestMapping.headers());
        String resultType = detectResultType(method, requestMapping);
        action.setViewRender(renderManager.get(resultType));
        action.setContentType(detectContentType(requestMapping, resultType));
        action.setToken("".equals(requestMapping.token()) ? AutumnId.instance().generate() : requestMapping.token());
        action.setRequestUrl(requestMapping.value().equals("/") ? requestPath : (requestPath += requestMapping.value()));
        action.setInterceptors(getInterceptors(applicationContext, action));
        return action;
    }

    private static String detectResultType(Method method, RequestMapping requestMapping)
    {
        String resultType;
        if (requestMapping.resultType() == DefaultResultType.None && method.getReturnType() != Void.class)
        {
            Class<?> returnType = method.getReturnType();
            if (returnType == String.class)
            {
                resultType = DefaultResultType.Str;
            }
            else if (returnType == ModelAndView.class)
            {
                resultType = DefaultResultType.Beetl;
            }
            else if (returnType == byte[].class)
            {
                resultType = DefaultResultType.Bytes;
            }
            else
            {
                resultType = DefaultResultType.Json;
            }
        }
        else
        {
            resultType = requestMapping.resultType();
        }
        return resultType;
    }

    private static String detectContentType(RequestMapping requestMapping, String resultType)
    {
        if (requestMapping.contentType().equals(""))
        {
            if (resultType.equals(DefaultResultType.Json))
            {
                return ContentType.JSON;
            }
            else if (resultType.equals(DefaultResultType.Html))
            {
                return ContentType.HTML;
            }
            else if (resultType.equals(DefaultResultType.Str))
            {
                return ContentType.STRING;
            }
            else if (resultType.equals(DefaultResultType.Bytes))
            {
                return ContentType.STREAM;
            }
            else if (resultType.equals(DefaultResultType.Beetl))
            {
                return ContentType.HTML;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return requestMapping.contentType();
        }
    }
    static ParamResolver[] generateParamResolvers(Method method)
    {
        if (method.isAnnotationPresent(RequestBody.class))
        {
            return new ParamResolver[]{new RequestBodyResolver(method)};
        }
        String[]            paramNames            = getParamNames(method);
        Class<?>[]          parameterTypes        = method.getParameterTypes();
        Annotation[][]      parameterAnnotations  = method.getParameterAnnotations();
        Type[]              genericParameterTypes = method.getGenericParameterTypes();
        List<ParamResolver> resolvers             = new ArrayList<ParamResolver>();
        nextResolver:
        for (int i = 0; i < parameterTypes.length; i++)
        {
            Class<?>     target      = parameterTypes[i];
            String       paramName   = paramNames[i];
            Annotation[] annotations = parameterAnnotations[i];
            Type         type        = genericParameterTypes[i];
            for (Annotation annotation : annotations)
            {
                if (annotation instanceof CookieValue)
                {
                    resolvers.add(new CookieResolver(target, paramName, (CookieValue) annotation));
                    continue nextResolver;
                }
                else if (annotation instanceof HeaderValue)
                {
                    resolvers.add(new HeaderResolver(target, paramName, (HeaderValue) annotation));
                    continue nextResolver;
                }
            }
            if (target.isPrimitive())
            {
                resolvers.add(new SingleValueResolver(paramName, target));
            }
            else if (target == Date.class)
            {
                resolvers.add(new DateResolver(paramName, annotations));
            }
            else if (target == HttpServletRequest.class)
            {
                resolvers.add(new HttpServletRequestResolver(paramName));
            }
            else if (target == HttpServletResponse.class)
            {
                resolvers.add(new HttpServletResponseResolver(paramName));
            }
            else if (target == HttpSession.class)
            {
                resolvers.add(new HttpSessionResolver(paramName));
            }
            else if (target == UploadItem.class)
            {
                resolvers.add(new SingleUploadResolver(paramName));
            }
            else if (target == List.class)
            {
                Type typeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
                if (typeArgument instanceof Class && typeArgument == UploadItem.class)
                {
                    resolvers.add(new ListUploadResolver(paramName));
                }
                else
                {
                    resolvers.add(new ListResolver(paramName, type));
                }
            }
            else if (target.isArray())
            {
                resolvers.add(new ArrayResolver(paramName, target));
            }
            else if (Enum.class.isAssignableFrom(target))
            {
                resolvers.add(new EnumResolver(paramName, target));
            }
            else
            {
                resolvers.add(new BeanResolver(paramName, target));
            }
        }
        return resolvers.toArray(new ParamResolver[resolvers.size()]);
    }

    static String[] getParamNames(Method method)
    {
        String[] paramNames;
        try
        {
            paramNames = BytecodeUtil.parseMethodParamNames(method);
        }
        catch (Exception e)
        {
            paramNames = new String[method.getParameterTypes().length];
        }
        Annotation[][] annos = method.getParameterAnnotations();
        for (int i = 0; i < annos.length; i++)
        {
            if (annos[i].length == 0)
            {
                continue;
            }
            else
            {
                for (Annotation each : annos[i])
                {
                    if (each instanceof RequestParam)
                    {
                        paramNames[i] = ((RequestParam) each).value();
                        break;
                    }
                }
            }
        }
        return paramNames;
    }

    static ActionInterceptor[] getInterceptors(ApplicationContext jfire, final Action info)
    {
        class Helper
        {
            boolean isIncluded(String includePath)
            {
                if (StringUtil.isNotBlank(includePath))
                {
                    if ("*".equals(includePath))
                    {
                        return true;
                    }
                    else
                    {
                        for (String singleInRule : includePath.split(";"))
                        {
                            if (isInterceptored(info.getRequestUrl(), singleInRule))
                            {
                                return true;
                            }
                        }
                    }
                }
                return false;
            }

            boolean isExcluded(String excludePath)
            {
                if (StringUtil.isNotBlank(excludePath))
                {
                    if ("*".equals(excludePath))
                    {
                        return true;
                    }
                    else
                    {
                        for (String singleExRule : excludePath.split(";"))
                        {
                            if (isInterceptored(info.getRequestUrl(), singleExRule))
                            {
                                return true;
                            }
                        }
                    }
                }
                return false;
            }

            boolean isInterceptored(String requestPath, String rule)
            {
                String[] rules = rule.split("\\*");
                int      index = 0;
                for (int i = 0; i < rules.length; i++)
                {
                    index = requestPath.indexOf(rules[i], index);
                    if (index < 0)
                    {
                        return false;
                    }
                    index += rules[i].length();
                }
                return true;
            }

            boolean hitToken(String token)
            {
                return StringUtil.isNotBlank(token) && token.equals(info.getToken());
            }

            ActionInterceptor[] getAllActionInterceptors(ApplicationContext jfire)
            {
                return jfire.getBeans(ActionInterceptor.class).toArray(new ActionInterceptor[0]);
            }
        }
        Helper                  helper       = new Helper();
        List<ActionInterceptor> interceptors = new ArrayList<ActionInterceptor>();
        for (ActionInterceptor interceptor : helper.getAllActionInterceptors(jfire))
        {
            if (helper.isExcluded(interceptor.excludePath()))
            {
                continue;
            }
            if (helper.isIncluded(interceptor.includePath()))
            {
                interceptors.add(interceptor);
                continue;
            }
            if (helper.hitToken(interceptor.tokenRule()))
            {
                interceptors.add(interceptor);
            }
        }
        Collections.sort(interceptors, new Comparator<ActionInterceptor>()
        {

            @Override
            public int compare(ActionInterceptor o1, ActionInterceptor o2)
            {
                return o1.order() - o2.order();
            }
        });
        return interceptors.toArray(new ActionInterceptor[interceptors.size()]);
    }
}
